Accéder à un serveur qui tourne sur Google Colab
Google Colab est une excellente façon de créer des Jupyter notebooks (*.ipynb) en bénéficiant gratuitement de puissance de calcul CPU / GPU.
Le problème c’est que lorsqu’on crée une application de type « serveur » comme Uvicron, Flask, Streamlit, … (qui s’accède normalement depuis une URL locale du genre « 127.0.0.1:8000 »), il n’est pas possible d’y accéder directement.
TLDR;
- Quand on lance un serveur depuis Google Colab, on est censé y accéder depuis une IP locale.
Par exemple : http://127.0.0.1:8000
Cette IP n’est pas accessible. Même en utilisant l’IP publique du serveur, ça ne fonctionne pas. - On peut utiliser
ngrok
qui permet de créer un tunnel vers un serveur accessible publiquement.
Le problème dengrok
est qu’il nous fait changer d’IP à chaque exécution, ce qui est pénible lorsqu’on est en phase de développement / debug. - Une solution est de créer un tunnel SSH avec redirection de port vers un serveur public et de faire en sorte que la connexion tourne en tache de fond avec
nohup
.
Création d’un serveur simple sur Google Colab
Voici à quoi ressemble un Google Colab proposant un simple serveur Uvicorn / FastAPI :
!pip install FastAPI uvicorn nest_asyncio
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
)
@app.get("/")
async def root():
return {"message": "Hello World !"}
import uvicorn
import nest_asyncio
# Apply nest_asyncio to allow running uvicorn in Colab
nest_asyncio.apply()
uvicorn.run(app, port=8000)
Lorsqu’on exécute ce code, on obtient :
INFO: Started server process [303] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
On constate que l’URL proposée est une URL locale et qu’on ne peut pas y accéder.
Créer un tunnel avec Ngrok
Ngrok est un service qui permet (entre autres) de servir des applications web depuis une URL statique.
C’est par exemple utile lorsqu’on développe quelque chose en local sur son poste et qu’on souhaite le présenter à un collègue sans passer par la publication sur un serveur web.
NGroq propose une version gratuite qui permet d’accéder à quelques fonctionnalités, qui nous suffiront pour notre cas d’usage : https://ngrok.com/pricing
Récupérer une clé d’API sur NGrok
Une fois son compte NGrok créé, il faut aller à l’URL https://dashboard.ngrok.com/get-started/your-authtoken pour récupérer sa clé d’API.
Ajouter la clé d’API en tant que « secret » sur Colab
Le meilleur moyen de ne pas enregistrer directement ses clés d’API dans le Jupiter Notebook, c’est de le stocker en tant que « secret » dans Google Colab.
On y accède depuis l’icône en forme de clé dans le panneau de gauche. On ajoute un « secret » auquel on donne le nom NGROK_API_KEY
. On s’assure qu’il est accessible depuis ce notebook.
Pour accéder à un « secret » depuis le code Python, on fait :
from google.colab import userdata
userdata.get('secretName')
Lancer Uvicorn en passant par Ngrok
Pour lancer notre serveur Uvicorn en passant par NGrok, on utilisera le code suivant :
!pip install pyngrok
from pyngrok import ngrok
import uvicorn
import nest_asyncio
from google.colab import userdata
# Get your authtoken from https://dashboard.ngrok.com/get-started/your-authtoken
auth_token = userdata.get('NGROK_API_KEY')
ngrok.set_auth_token(auth_token)
ngrok_tunnel = ngrok.connect(8000)
print('Public URL:', ngrok_tunnel.public_url)
nest_asyncio.apply()
uvicorn.run(app, port=8000)
Ce code va créer un tunnel entre le port 8000
de notre instance locale et un serveur hébergé par Ngrok accessible par une URL publique, que l’on affiche.
En lançant notre serveur Uvicorn sur le port 8000
, il sera alors accessible par l’URL publique.
Le code suivant va retourner :
INFO: Started server process [1655] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) Public URL: https://1234-56-78-91-011.ngrok-free.app
l’inconvénient de cette technique, c’est que lorsqu’on arrête le serveur Uvicorn, le tunnel avec Ngrok est interrompu. Lorsqu’on relancera le serveur Uvicorn, notre tunnel Ngrok sera sur une nouvelle URL.
Pour éviter ça, il y a plusieurs solutions :
- Prendre un abonnement payant chez Ngrok qui permet d’avoir des URL réutilisables.
- Utiliser une autre méthode pour faire son tunnel.
Jupiter notebook de l’exemple : https://github.com/Yajusta/GoogleColab/blob/main/POC/FastAPI_with_NGROK_tunnel_POC.ipynb
Créer un tunnel SSH avec redirection de port
Il existe plusieurs services qui permettent de rediriger un port local vers une URL publique grâce à un tunnel SSH.
Par exemple :
Le problème avec cette technique, c’est que si on crée le tunnel depuis un script Python, dès qu’on arrête l’exécution du code Python le tunnel s’arrête et on changera (encore) d’URL publique au prochain lancement de notre serveur.
L’astuce consiste donc a exécuter le tunnel SSH depuis une commande shell, tout en la laissant tourner en arrière plan.
Pour cela, nous utilisons la commande nohup
couplée à &
. Lorsqu’on utilise nohup
, la sortie standard n’est pas affichée mais enregistrée dans un fichier texte (nohup.out
). Il faut donc afficher le contenu de ce fichier afin de connaitre l’URL publique qui nous est attribuée.
Le script suivant permet de faire toutes ces actions, et même supprimer les anciennes connexions SSH potentiellement existantes.
!rm -f nohup.out
!ps aux | grep '[s]sh' | awk '{print $2}' | xargs kill -9
!nohup ssh -o StrictHostKeyChecking=no -R 80:localhost:8000 serveo.net &
!ps -edf | grep ssh
!sleep 3
!cat nohup.out
En détail :
!rm -f nohup.out
: on supprime le fichiernohup.out
qui a potentiellement été créé lors de la dernière exécution. Commenohup
concatène dans le fichier de sortie, on ne souhaite pas afficher les informations qui concernent les précédentes exécutions.!ps aux | grep '[s]sh' | awk '{print $2}' | xargs kill -9
: on recherche s’il y a des processssh
en train de tourner et on les tue aveckill -9
. L’utilisation de[s]sh
permet de faire en sorte que la commande en elle même ne ressorte pas dans legrep
.!nohup ssh -o StrictHostKeyChecking=no -R 80:localhost:8000 serveo.net &
: on redirige le port8000
local vers le port80
deserveo.net
.-o StrictHostKeyChecking=no
permet de ne pas avoir de question sur les clés de sécurité SSH.&
permet de faire en sorte que le processus soit lancer en arrière plan.!sleep 3
: on attend 3 secondes, le temps de laisser le service effectuer sa connexion.cat nohup.out
: on affiche sur la sortie standard le contenu du fichiernohup.out
, c’est à dire la réponse de la connexion SSH.
La sortie ressemblera à :
nohup: appending output to 'nohup.out'
root 33330 1 0 17:02 ? 00:00:00 ssh -o StrictHostKeyChecking=no -R 80:localhost:
root 33331 303 0 17:02 ? 00:00:00 /bin/bash -c ps -edf | grep ssh
root 33333 33331 0 17:02 ? 00:00:00 grep ssh
Pseudo-terminal will not be allocated because stdin is not a terminal.
Forwarding HTTP traffic from https://1234567890azertyuiopqsdfghjklm.serveo.net
L’URL https://1234567890azertyuiopqsdfghjklm.serveo.net sera donc notre URL publique d’accès.
Grâce à cette méthode, on peut lancer notre serveur Uvicorn et le stopper autant de fois qu’on le souhaite, notre URL publique restera la même pendant tout le temps de notre session !
Jupiter notebook de l’exemple : https://github.com/Yajusta/GoogleColab/blob/main/POC/FastAPI_with_SSH_tunnel_POC.ipynb
Jupiter notebook d’exemple avec Streamlit : https://github.com/Yajusta/GoogleColab/blob/main/POC/Streamlit_with_SSH_tunnel_POC.ipynb